package choreganizer;

import java.text.*;
import java.util.*;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.xml.sax.*;
import org.xml.sax.helpers.*; 



public class ScheduleReader
extends DefaultHandler
{
	
	// Variables for getting chores in a specific date
	private List<ChoreSchedule> choreSchedules;
	private int matchYear;
	private int matchMonth;
	
	// Variables for getting all schedules
	private List<ScheduleData> scheduleData;
	private boolean getAll;
	
	// Temp variables used to store info about a schedule
	private String currElementName;
	private ScheduleData currSchedItem;
	private boolean addCurrSchedItem;
	
	
	
	
	
	/*
	* name:		Overloaded Constructor
	* params:	boolean getAll - Do we want to read in all Schedule items
	* return:	ScheduleReader
	* desc:		Should only be called when getAll is set to true, so it will
	*				create a ScheduleData array to hold all possible schedules
	*/
	public ScheduleReader(boolean getAll)
	{
		this.getAll = getAll;
		scheduleData = new Vector<ScheduleData>();
	}
	
	
	/*
	* name:		Overloaded Constructor
	* params:	int matchMonth - the month to find scheduled items for
	*				int matchYear  - the year to find scheduled items for
	* return:	ScheduleReader
	* desc:		Will attempt to find all scheduled items which are set
	*				to occur in the given year/month, and put them into a
	*				ChoreSchedule array, so they can be linked to their
	*				appropriate ChoreData info
	*/
	public ScheduleReader(int matchMonth, int matchYear)
	{
		this.matchYear = matchYear;
		this.matchMonth = matchMonth;
		choreSchedules = new Vector<ChoreSchedule>();
	}
	
	
	
	
	
	
	
	
	
	
	
	/*
	* name:		getAllSchedules()
	* params:	none
	* return:	Vector<ScheduleData> - collection of all scheduled items
	* desc:		We should only call this method when we've searched
	*				for all possible scheduled items
	*/
	public java.util.List<ScheduleData> getAllSchedules()
	{
		return scheduleData;
	}

	
	/*
	* name:		getChoreSchedules()
	* params:	none
	* return:	Vector<ChoreSchedule> - collection of schdules for a given time
	* desc:		We should only call this method when we've searched for scheduled
	*				items for a given year/month
	*/
	public java.util.List<ChoreSchedule> getChoreSchedules()
	{
		return choreSchedules;
	}
	
	
	
	
	
	
	
	
	
	
	
	/*
	* name:		startElement()
	* params:	String namespaceURI - namespace
	*				String lName - 
	*				String qName - qualified element name
	*				Attributes - attributes for the current element
	* return:	none
	* desc:		Holds the name of an element tag, and all it's attributes
	*				We'll parse this info to create a ScheduleData item
	*/
	public void startElement(String namespaceURI, String lName, String qName, Attributes attrs) 
	throws SAXException
	{
		String eName = lName;
		if ( "".equals(eName) ) 
			eName = qName;
		
		currElementName = eName;
		
		// Reset variables for the new schedule item
		if ( eName.equals("item") )
		{
			currSchedItem = new ScheduleData();
			addCurrSchedItem = true;
		}
		
		// Store variables
		if (attrs != null)
		{
			for (int i=0; i < attrs.getLength(); i++)
			{
				String aVal = attrs.getValue(i).trim();
				String aName = attrs.getLocalName(i);
				if ("".equals(aName))
					aName = attrs.getQName(i);
				
				if (eName.equals("item") && aName.equals("id"))
				{
					currSchedItem.setID(aVal);
				}
				
				else if (eName.equals("item") && aName.equals("dateAdded"))
				{
					currSchedItem.setDateAdded(aVal);
				}
				
				else if (eName.equals("scheduling") && aName.equals("start"))
				{
					currSchedItem.setStartDate(aVal);
				}
				
				else if (eName.equals("year") && aName.equals("type"))
				{
					currSchedItem.setYearType( ScheduleTypes.YearToType(aVal) );
				}
				
				else if (eName.equals("month") && aName.equals("type"))
				{
					currSchedItem.setMonthType( ScheduleTypes.MonthToType(aVal) );
				}
				
				else if (eName.equals("day") && aName.equals("type"))
				{
					currSchedItem.setDayType( ScheduleTypes.DayToType(aVal) );
				}
				
				else if (eName.equals("time") && aName.equals("type"))
				{
					currSchedItem.setTimeType( ScheduleTypes.TimeToType(aVal) );
				}
				
				else if (eName.equals("alert") && aName.equals("type"))
				{
					currSchedItem.setAlertType( ScheduleTypes.AlertToType(aVal) );
				}
			}
		}
		
	}
	
	
	/*
	* name:		characters()
	* params:	char buf[] - array of characters read in
	*				int offset - offset from the buffer
	*				int len - number of characters in the buffer
	* return:	none
	* desc:		This will allow us to read the data in a tag
	*				We'll parse this info to create a ScheduleData item
	*/
	public void characters(char buf[], int offset, int len)
	throws SAXException
	{
		String s = new String(buf, offset, len);
		s = s.trim();
		
		// Break out if the string is empty
		if ( s.equals("") )
			return;
		
		if ( currElementName.equals("choreID") )
		{
			currSchedItem.setChoreID( s );
		}
		
		else if ( currElementName.equals("year") )
		{
			// RegEx to weed out any spaces and/or commas
			String[] result = s.split("[\\s,]+");
			currSchedItem.setScheduledYear( result );
		}
		
		else if ( currElementName.equals("month") )
		{
			String[] result = s.split("[\\s,]+");
			currSchedItem.setScheduledMonth( result );
		}
		
		else if ( currElementName.equals("day"))
		{
			String[] result = s.split("[\\s,]+");
			currSchedItem.setScheduledDay( result );
		}
		
		else if ( currElementName.equals("time") )
		{
			String[] result = s.split("[\\s,]+");
			currSchedItem.setScheduledTime( result );
		}
	}
	
	
	/*
	* name:		endElement()
	* params:	String namespaceURI - namespace
	*				sName - tag elements name
	*				qName - qualified name
	* return:	none
	* desc:		Tells us when we've hit the end of a tag element
	*				We'll use this as a que to check and/or add the 
	*				ScheduleData item we've been assembling
	*/
	public void endElement(String namespaceURI, String sName, String qName )
	throws SAXException
	{
		// We've assembled a scheduled item
		if ( sName.equals("item") )
		{
			// If we want all scheduled items, put it into our ScheduleData list
			if ( getAll)
			{
				scheduleData.add( currSchedItem );
			}
			
			// If we only want scheduled items in a given month/year, perform a check
			else
			{
				// if it is in matchMonth/matchYear, put it into our ChoreSchedule list
				if ( checkYear() && checkMonth() && checkDay() )
				{
					ChoreSchedule cs = new ChoreSchedule();
					cs.setScheduleData( currSchedItem );
					cs.createDayList( matchMonth, matchYear );
					choreSchedules.add( cs );
				}
			}
		}
	}
	
	
	
	
	
	
	
	
	
	
	/*
	* name:		checkDay()
	* params:	none
	* return:	boolean
	* desc:		Checks the current ScheduleData item we've created to see if it's
	*				set up to occur annually, and if so, if it's scheduled to occur in
	*				the set month/year
	*/
	private boolean checkDay()
	{
		// Only need to check if the month type is unspecified and day type is annual
		if ( currSchedItem.getMonthType() == ScheduleTypes.MonthTypes.unspecified )
		{
			// if monthType is unspecified, dayType should always be annual.  We make them
			// seperate if statements just to make sure though
			if ( currSchedItem.getDayType() == ScheduleTypes.DayTypes.annual )
			{
				// Convert the string into a date (leave out the hour/min)
				Date startDate = new Date();
				try {
					startDate = new SimpleDateFormat("MM/dd/yyyy").parse( currSchedItem.getStartDate() );
				} catch (Exception e) {
					System.err.println("Error converting schedule start date to date object");
				}
				
				// Start time for the scheduled item
				GregorianCalendar checkTime = new GregorianCalendar();
				checkTime.setTime( startDate );
				// Beginning of the NEXT month we're checking against
				GregorianCalendar maxTime = new GregorianCalendar( matchYear, matchMonth+1, 1 );
				
				// Number of days we're annually adding (should be the first/only item in our string array)
				int addDays = Integer.decode( currSchedItem.getScheduledDay()[0] );
				
				while ( maxTime.after(checkTime) )
				{
					// Keep adding to checkTime to see if it falls in the matchYear/matchMonth
					checkTime.add( GregorianCalendar.DATE, addDays );

					if ( checkTime.get(Calendar.MONTH) == matchMonth && checkTime.get(Calendar.YEAR) == matchYear)
						return true;
				}
			}
			
			// If we didn't find a match, return false
			return false;
		}
		
		// MonthType isn't unspecified, return true
		return true;
	}
	
	
	/*
	* name:		checkMonth()
	* params:	none
	* return:	boolean
	* desc:		Checks the current ScheduleData item we've created to see if it's
	*				it's scheduled to occur in the set month
	*/
	private boolean checkMonth()
	{
		Date startDate = new Date();
		try {
			startDate = new SimpleDateFormat("MM/dd/yyyy HH:mm").parse( currSchedItem.getStartDate() );
		} catch (Exception e) {
			System.err.println("Error converting schedule start date to date object");
		}
		
		// Start time for the scheduled item
		GregorianCalendar startCal = new GregorianCalendar();
		startCal.setTime( startDate );
		
		if ( matchYear == startCal.get(Calendar.YEAR) && matchMonth == startCal.get(Calendar.MONTH) )
		{
			return true;
		}
		
		else if ( currSchedItem.getMonthType() == ScheduleTypes.MonthTypes.unspecified )
		{
			return true;
		}
		
		else if ( currSchedItem.getMonthType() == ScheduleTypes.MonthTypes.specific )
		{
			for (String s: currSchedItem.getScheduledMonth())
			{
				int intMonth = Integer.decode( s );
				if (matchMonth == intMonth)
					return true;
			}
		}
		
		else if ( currSchedItem.getMonthType() == ScheduleTypes.MonthTypes.annual )
		{
			for (String s: currSchedItem.getScheduledMonth())
			{
				int intMonth = Integer.decode( s );
				int monthCheck = startCal.get(Calendar.MONTH);
				int yearCheck = startCal.get(Calendar.YEAR);
				
				while (matchYear >= yearCheck)
				{
					// SHOULD IT BE 12?!?!
					if (monthCheck > 12)
					{
						int quotient = (int)(monthCheck/12);
						int remainder = (int)(monthCheck%12);
						
						yearCheck += quotient;
						monthCheck = remainder;
					}

					if (matchYear == yearCheck && matchMonth == monthCheck)
						return true;
					
					monthCheck += intMonth;
				}
			}
		}
		
		return false;
	}
	
	
	/*
	* name:		checkYear()
	* params:	none
	* return:	boolean
	* desc:		Checks the current ScheduleData item we've created to see if it's
	*				it's scheduled to occur in the set year
	*/
	private boolean checkYear()
	{
		Date startDate = new Date();
		try {
			startDate = new SimpleDateFormat("MM/dd/yyyy HH:mm").parse( currSchedItem.getStartDate() );
		} catch (Exception e) {
			System.err.println("Error converting schedule start date to date object");
		}
		
		// Start time for the scheduled item
		GregorianCalendar startCal = new GregorianCalendar();
		startCal.setTime( startDate );

		
		if ( matchYear == startCal.get(Calendar.YEAR) )
		{
			return true;
		}
		
		else if ( currSchedItem.getYearType() == ScheduleTypes.YearTypes.specific )
		{
			for (String s: currSchedItem.getScheduledYear())
			{
				int intYear = Integer.decode( s );
				if (matchYear == intYear)
					return true;
			}
		}
		
		else if ( currSchedItem.getYearType() == ScheduleTypes.YearTypes.annual )
		{
			for (String s: currSchedItem.getScheduledYear())
			{
				int intYear = Integer.decode( s );
				int yearCheck = startCal.get(Calendar.YEAR);
				
				while (matchYear > yearCheck)
				{
					yearCheck += intYear;
					if (matchYear == yearCheck)
						return true;
				}
			}
		}
		return false;
	}
	
}